#pragma once

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <cassert>

#define ONE_BIT_SET(x) is_power_of_2((x))
#define BOARD_INDEX(x, y) ((x) + (I_BOARD_RANK * (y)))

// struct-level prototypes
I_CHESS_POSITION* empty_board();
I_CHESS_POSITION* start_board();

// create zobrist keys and locks
void create_zobrist(ZOBRIST, ZOBRIST);

// setup all values to initial values
void all_setup();

// convert 12x12 to and from 8x8
inline int convert_144_to_64(int);
inline int convert_64_to_144(int);

// static evaluation helper
void eval_material_value(I_CHESS_POSITION*);

// apply and undo a move object to/from a chess board
void apply_move(I_CHESS_POSITION*, I_CHESS_MOVE*);
void undo_move(I_CHESS_POSITION*, I_CHESS_MOVE*);

// get the key and lock from a chess board
ZRANDOM board_hash(I_CHESS_POSITION*);
ZRANDOM board_lock(I_CHESS_POSITION*);
ZRANDOM board_hashelem(I_CHESS_POSITION*, ZOBRIST*);

// get move string
MOVE_NAME move_to_algL(I_CHESS_MOVE*);

// move and movelist setup/generator functions
inline I_CHESS_MOVE* setup_move(I_CHESS_POSITION*);
MOVELIST gen_move_list(I_CHESS_POSITION*);
inline void free_move_list(MOVELIST);

// generate legal moves for each piece type
void add_legal_moves_PA(I_CHESS_POSITION*, int, MOVELIST&);
void add_legal_moves_KN(I_CHESS_POSITION*, int, MOVELIST&);
void add_legal_moves_BI(I_CHESS_POSITION*, int, MOVELIST&);
void add_legal_moves_RO(I_CHESS_POSITION*, int, MOVELIST&);
void add_legal_moves_KI(I_CHESS_POSITION*, int, MOVELIST&);

// for use in the evaluation function
int count_moves_KI(I_CHESS_POSITION*);

// checking check and attack situations
bool under_attack(I_CHESS_POSITION*, int, int);
bool in_check(I_CHESS_POSITION*, int);

// endgame conditions
bool checkmate(I_CHESS_POSITION*, int);
bool stalemate(I_CHESS_POSITION*, int);
bool mate_help(I_CHESS_POSITION*, int);

// shorthand
bool in_check(I_CHESS_POSITION*);
bool checkmate(I_CHESS_POSITION*);
bool stalemate(I_CHESS_POSITION*);

// main chess driver
I_CHESS_MOVE* select_best_move(I_CHESS_POSITION*, int);
MOVE_NAME apply_best_move(I_CHESS_POSITION*, int);

// bitboard functions
I_BITBOARD get_bitboard(I_CHESS_POSITION*, int);
I_BITBOARD get_bitboard(I_CHESS_POSITION*, int, int);

// obscure functions
void mt_setup(unsigned int);
inline unsigned int mt_rand32();
inline void mt_generate();

inline bool is_power_of_2(int);

inline bool is_piece_type(int, int);
inline int get_piece_type(int);

inline bool is_white_sq(int);
inline bool is_white_sq(int, int);

// ambiguous distance function
inline int board_distance(int, int, int, int, int);
inline int board_distance(int, int, int);

// distance functions (worker functions)
inline int taxi_distance(int, int, int, int);
inline int taxi_distance_rot(int, int, int, int);
inline int cheb_distance(int, int, int, int);

// distance functions (driver functions)
inline int taxi_distance(int, int);
inline int taxi_distance_rot(int, int);
inline int cheb_distance(int, int);

// overloaded ostream operator, for board output
std::ostream& operator<<(std::ostream&, const I_CHESS_POSITION*);
